/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactDOMTextComponent
 */

'use strict'

var _assign = require('object-assign')

var DOMChildrenOperations = require('./DOMChildrenOperations')
var DOMLazyTree = require('./DOMLazyTree')
var ReactDOMComponentTree = require('./ReactDOMComponentTree')
var ReactPerf = require('./ReactPerf')

var escapeTextContentForBrowser = require('./escapeTextContentForBrowser')
var invariant = require('fbjs/lib/invariant')
var validateDOMNesting = require('./validateDOMNesting')

/**
 * Text nodes violate a couple assumptions that React makes about components:
 *
 *  - When mounting text into the DOM, adjacent text nodes are merged.
 *  - Text nodes cannot be assigned a React root ID.
 *
 * This component is used to wrap strings between comment nodes so that they
 * can undergo the same reconciliation that is applied to elements.
 *
 * TODO: Investigate representing React components in the DOM with text nodes.
 *
 * @class ReactDOMTextComponent
 * @extends ReactComponent
 * @internal
 */
var ReactDOMTextComponent = function (text) {
  // TODO: This is really a ReactText (ReactNode), not a ReactElement
  this._currentElement = text
  this._stringText = '' + text
  // ReactDOMComponentTree uses these:
  this._nativeNode = null
  this._nativeParent = null

  // Properties
  this._domID = null
  this._mountIndex = 0
  this._closingComment = null
  this._commentNodes = null
}

_assign(ReactDOMTextComponent.prototype, {
  /**
   * Creates the markup for this text node. This node is not intended to have
   * any features besides containing text content.
   *
   * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
   * @return {string} Markup for this text node.
   * @internal
   */
  mountComponent: function (
    transaction,
    nativeParent,
    nativeContainerInfo,
    context
  ) {
    if (process.env.NODE_ENV !== 'production') {
      var parentInfo
      if (nativeParent != null) {
        parentInfo = nativeParent._ancestorInfo
      } else if (nativeContainerInfo != null) {
        parentInfo = nativeContainerInfo._ancestorInfo
      }
      if (parentInfo) {
        // parentInfo should always be present except for the top-level
        // component when server rendering
        validateDOMNesting('#text', this, parentInfo)
      }
    }

    var domID = nativeContainerInfo._idCounter++
    var openingValue = ' react-text: ' + domID + ' '
    var closingValue = ' /react-text '
    this._domID = domID
    this._nativeParent = nativeParent
    if (transaction.useCreateElement) {
      var ownerDocument = nativeContainerInfo._ownerDocument
      var openingComment = ownerDocument.createComment(openingValue)
      var closingComment = ownerDocument.createComment(closingValue)
      var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment())
      DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment))
      if (this._stringText) {
        DOMLazyTree.queueChild(
          lazyTree,
          DOMLazyTree(ownerDocument.createTextNode(this._stringText))
        )
      }
      DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment))
      ReactDOMComponentTree.precacheNode(this, openingComment)
      this._closingComment = closingComment
      return lazyTree
    } else {
      var escapedText = escapeTextContentForBrowser(this._stringText)

      if (transaction.renderToStaticMarkup) {
        // Normally we'd wrap this between comment nodes for the reasons stated
        // above, but since this is a situation where React won't take over
        // (static pages), we can simply return the text as it is.
        return escapedText
      }

      return (
        '<!--' +
        openingValue +
        '-->' +
        escapedText +
        '<!--' +
        closingValue +
        '-->'
      )
    }
  },

  /**
   * Updates this component by updating the text content.
   *
   * @param {ReactText} nextText The next text content
   * @param {ReactReconcileTransaction} transaction
   * @internal
   */
  receiveComponent: function (nextText, transaction) {
    if (nextText !== this._currentElement) {
      this._currentElement = nextText
      var nextStringText = '' + nextText
      if (nextStringText !== this._stringText) {
        // TODO: Save this as pending props and use performUpdateIfNecessary
        // and/or updateComponent to do the actual update for consistency with
        // other component types?
        this._stringText = nextStringText
        var commentNodes = this.getNativeNode()
        DOMChildrenOperations.replaceDelimitedText(
          commentNodes[0],
          commentNodes[1],
          nextStringText
        )
      }
    }
  },

  getNativeNode: function () {
    var nativeNode = this._commentNodes
    if (nativeNode) {
      return nativeNode
    }
    if (!this._closingComment) {
      var openingComment = ReactDOMComponentTree.getNodeFromInstance(this)
      var node = openingComment.nextSibling
      while (true) {
        !(node != null)
          ? process.env.NODE_ENV !== 'production'
            ? invariant(
                false,
                'Missing closing comment for text component %s',
                this._domID
              )
            : invariant(false)
          : void 0
        if (node.nodeType === 8 && node.nodeValue === ' /react-text ') {
          this._closingComment = node
          break
        }
        node = node.nextSibling
      }
    }
    nativeNode = [this._nativeNode, this._closingComment]
    this._commentNodes = nativeNode
    return nativeNode
  },

  unmountComponent: function () {
    this._closingComment = null
    this._commentNodes = null
    ReactDOMComponentTree.uncacheNode(this)
  }
})

ReactPerf.measureMethods(
  ReactDOMTextComponent.prototype,
  'ReactDOMTextComponent',
  {
    mountComponent: 'mountComponent',
    receiveComponent: 'receiveComponent'
  }
)

module.exports = ReactDOMTextComponent
